/*
 * Borrowed from UIP Project
 * By:
 *  Manuel Rebollo
 *  Nosheen Zaza
 *  Seyedsaeid Mahpeikar
 *  Wo Ke
 */
package vical.data;

import java.io.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jdom.*;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;
import vical.model.Entry;

/**
 * Reads and writes entry elements to an XML file. <br/>
 * This is a singleton class. It is so to make it easier to all clients
 * to access the only instance through the call {@code DataManager.getInstance()},
 * and to prevent having multiple file access conflicts. <br/>
 * It uses the JDOM supporting library, and thus it does not try to provide 
 * tools for accessing, reading or creating XML files.
 * It rather does the specific entrys needed for our application.
 * Since JDOM takes care of all XML operations. <br/>
 * For performance reasons, only newly added entrys are saved immediately to
 * the XML file, while edit and delete changes are saved when the application
 * is closed.
 *
 * @author
 * Manuel Rebollo
 * @author
 * Nosheen Zaza
 * @author
 * Seyedsaeid Mahpeikar
 */
public class DataManager {

    private Document entryDoc;
    private static DataManager dataManager;

    /*
     * Since we want to allow the user in future releases to enter any desired
     * category and priority, we do not specify then as enums, and allow any
     * strings to represent them.
     */
    private InputStream xmlInputStream;
    /**
     * Specifies the path of the user data stored on the user machine.
     */
    private final String xmlPath = System.getProperty("user.home")
            + ResourceBundle.getBundle("vical/data/dataPreferences").getString("userDataPath");

    /**
     * Creates a document and loads the xml file to it
     */
    private DataManager() {
        initialize();
    }

    private void initialize() {
        try {
            File xmlFile = new File(xmlPath);
            /*
             * When running the application for the first time, copy
             * the XML file with sample entrys to a specified directory in
             * the user's home folder.
             */
            if (!xmlFile.exists()) {
                xmlFile.getParentFile().mkdirs();
                xmlInputStream = DataManager.class.getResourceAsStream("EntryList.xml");
                System.out.println(DataManager.class.getResource("EntryList.xml").getFile());
                FileOutputStream fos = new FileOutputStream(xmlPath);
                int readInt;
                while ((readInt = xmlInputStream.read()) != -1) {
                    fos.write(readInt);
                }
            }
            xmlInputStream = new FileInputStream(xmlPath);
            entryDoc = new Document();
            SAXBuilder builder = new SAXBuilder();

            try {
                entryDoc = builder.build(xmlInputStream);
            } catch (JDOMException ex) {
                Logger.getLogger(DataManager.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(DataManager.class.getName()).log(Level.SEVERE, null, ex);
            }
        } catch (FileNotFoundException fnex) {
            Logger.getLogger(DataManager.class.getName()).log(Level.SEVERE, null, fnex);
        } catch (IOException ioex) {
            Logger.getLogger(DataManager.class.getName()).log(Level.SEVERE, null, ioex);
        }
    }

    /**
     * Returns the count of entry tags in the xml file.
     * @return the count of entry tags.
     */
    public int getEntryCount() {
        return entryDoc.getRootElement().getChildren().size();

    }

    /**
     * Returns the only instance of {@code DataManager}.
     * @return The only {@code DataManager} instance.
     */
    public synchronized static DataManager getInstance() {
        if (dataManager == null) {
            dataManager = new DataManager();
        }
        return dataManager;
    }

    /**
     * Prevents cloning this singleton class.
     * @return Object
     * @throws CloneNotSupportedException
     */
    @Override
    public Object clone()
            throws CloneNotSupportedException {
        throw new CloneNotSupportedException();

    }

    /**
     * Saves the contents of {@code entryDoc} to the XML file.
     */
    public void save() {
        try {
            XMLOutputter outputter = new XMLOutputter();
            FileOutputStream file = new FileOutputStream(xmlPath);
            outputter.output(entryDoc, file);
            file.flush();
            file.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Adds a new entry to the XML document and saves it immediately.
     * @param entry The entry to be added.
     */
    public void addEntry(Entry entry) {
        // Gets the root tag
        Element rootTag = entryDoc.getRootElement();

        Element entryTag = new Element("entry");
        // Adds attributes
        //entryTag.setAttribute("Id", "" + (getEntryCount() + 1));
        entryTag.setAttribute("Id", "" + (entry.getId()));
        entryTag.setAttribute("Priority", "" + (entry.getPriority()));
        entryTag.setAttribute("Category", "" + (entry.getCategory()));
        entryTag.setAttribute("Name", entry.getName());
        entryTag.setAttribute("Description", entry.getDescription());
        entryTag.setAttribute("AllDay", String.valueOf(entry.isAllday()));
        entryTag.setAttribute("SpecialEvents", entry.getSpecialEvents());

        /*
         * Since we allow null dats, we need to handle them carefully.
         * We do not want to allow null dates to be processed.
         */
        if (entry.getStartTime() != null) {
            String year = String.valueOf(entry.getStartTime().get(Calendar.YEAR));
            String month = String.valueOf(entry.getStartTime().get(Calendar.MONTH));
            String day = String.valueOf(entry.getStartTime().get(Calendar.DAY_OF_MONTH));
            String hour = String.valueOf(entry.getStartTime().get(Calendar.HOUR));
            String minute = String.valueOf(entry.getStartTime().get(Calendar.MINUTE));
            entryTag.setAttribute("Start_time", year + " " + month + " " + day + " " + hour + " " + minute);
        }

        if (entry.getStartTime() != null) {
            String year = String.valueOf(entry.getEndTime().get(Calendar.YEAR));
            String month = String.valueOf(entry.getEndTime().get(Calendar.MONTH));
            String day = String.valueOf(entry.getEndTime().get(Calendar.DAY_OF_MONTH));
            String hour = String.valueOf(entry.getEndTime().get(Calendar.HOUR));
            String minute = String.valueOf(entry.getEndTime().get(Calendar.MINUTE));
            entryTag.setAttribute("End_time", year + " " + month + " " + day + " " + hour + " " + minute);
        }

        // Adds the new entry as child to the root tag.
        rootTag.addContent(entryTag);
        //save();
    }

    /**
     * Deletes a entry from {@code entryDoc}.
     * @param entry
     */
    public void deleteEntry(Entry entry) {
        deleteEntry(entry.getId());
    }

    public void deleteEntry(int entryId) {
        // Get the root tag
        Element rootTag = entryDoc.getRootElement();
        List<Element> entrys = rootTag.getChildren();
        entrys.remove(getElementByEntryId(entryId));
    }

    /**
     * Edits a entry.
     * @param entry
     */
    public void editEntry(Entry entry) {
        Element entryElement = getElementByEntryId(entry.getId());
        entryElement.setAttribute("Name", entry.getName());
        entryElement.setAttribute("Description", entry.getDescription());
        entryElement.setAttribute("Category", entry.getCategory());
        entryElement.setAttribute("Priority", entry.getPriority());
        entryElement.setAttribute("AllDay", String.valueOf(entry.isAllday()));
        entryElement.setAttribute("SpecialEvents", entry.getSpecialEvents());

        Calendar startTime = entry.getStartTime();
        String year = String.valueOf(startTime.get(Calendar.YEAR));
        String month = String.valueOf(startTime.get(Calendar.MONTH));
        String day = String.valueOf(startTime.get(Calendar.DAY_OF_MONTH));
        String hour = String.valueOf(startTime.get(Calendar.HOUR));
        String minute = String.valueOf(startTime.get(Calendar.MINUTE));
        entryElement.setAttribute("Start_time", year + " " + month + " " + day + " " + hour + " " + minute);

        Calendar endTime = entry.getEndTime();
        year = String.valueOf(endTime.get(Calendar.YEAR));
        month = String.valueOf(endTime.get(Calendar.MONTH));
        day = String.valueOf(endTime.get(Calendar.DAY_OF_MONTH));
        hour = String.valueOf(endTime.get(Calendar.HOUR));
        minute = String.valueOf(endTime.get(Calendar.MINUTE));
        entryElement.setAttribute("End_time", year + " " + month + " " + day + " " + hour + " " + minute);
        //save();
    }

    /**
     * Edits a entry's name.
     * @param entry
     * @param name
     */
    public void editEntryName(Entry entry, String name) {
        Element entryElement = getElementByEntryId(entry.getId());
        entryElement.setAttribute("Name", name);
    }

    /**
     * Edits a entry's description.
     * @param entry
     * @param description
     */
    public void editentryDescription(Entry entry, String description) {
        Element entryElement = getElementByEntryId(entry.getId());
        entryElement.setAttribute("Description", description);
    }

    /**
     * Edits a entry's category
     * @param entry
     * @param category
     */
    public void editEntryCategory(Entry entry, String category) {
        Element entryElement = getElementByEntryId(entry.getId());
        entryElement.setAttribute("Category", category);
    }

    /**
     * Edits  entry's priority
     * @param entry
     * @param priority
     */
    public void editEntryPriority(Entry entry, String priority) {
        Element entryElement = getElementByEntryId(entry.getId());
        entryElement.setAttribute("Priority", priority);
    }

    /**
     * Edits a entry's start time.
     * @param entry
     * @param startTime
     */
    public void editEntryStartTime(Entry entry, Calendar startTime) {
        Element entryElement = getElementByEntryId(entry.getId());
        String year = String.valueOf(startTime.get(Calendar.YEAR));
        String month = String.valueOf(startTime.get(Calendar.MONTH));
        String day = String.valueOf(startTime.get(Calendar.DAY_OF_MONTH));
        String hour = String.valueOf(startTime.get(Calendar.HOUR));
        String minute = String.valueOf(startTime.get(Calendar.MINUTE));
        entryElement.setAttribute("Start_time", year + " " + month + " " + day + " " + hour + " " + minute);
    }

    /**
     * Edits a entry's end time.
     * @param entry
     * @param endTime
     */
    public void editEntryEndTime(Entry entry, Calendar endTime) {
        Element entryElement = getElementByEntryId(entry.getId());
        String year = String.valueOf(endTime.get(Calendar.YEAR));
        String month = String.valueOf(endTime.get(Calendar.MONTH));
        String day = String.valueOf(endTime.get(Calendar.DAY_OF_MONTH));
        String hour = String.valueOf(endTime.get(Calendar.HOUR));
        String minute = String.valueOf(endTime.get(Calendar.MINUTE));
        entryElement.setAttribute("End_time", year + " " + month + " " + day + " " + hour + " " + minute);
    }

    // modified by Can
    public Element getElementByEntryId(int id) {
        Element rootTag = entryDoc.getRootElement();
        List<Element> entries = rootTag.getChildren();
        for (Element element : entries) {
            if (element.getAttributeValue("Id").equals("" + id)) {
                return element;
            }
        }
        return null;
    }

    /**
     * Returns all currently stored entries in the XML file.
     * @return {@code ArrayList} of all stored entries.
     */
    public ArrayList<Entry> getAllEntries() {
        return getAllEntries(null, true);
    }

    /**
     * Returns all currently stored entries in the XML file.
     * @param startDate all entries from the specified date
     * @return {@code ArrayList} of all stored entries.
     */
    public ArrayList<Entry> getAllEntries(Calendar startDate) {
        return getAllEntries(startDate, false);
    }

    /**
     * Returns all currently stored entries in the XML file.
     * @param startDate all entries from the specified date
     * @return {@code ArrayList} of all stored entries.
     */
    private ArrayList<Entry> getAllEntries(Calendar startDate, boolean all) {
        ArrayList<Entry> entryList = new ArrayList<Entry>();
        Element rootTag = entryDoc.getRootElement();
        List<Element> entrys = rootTag.getChildren();

        for (Element entryElement : entrys) {
            Entry entry = getEntryByElement(entryElement);

            if (all
                    || (startDate.get(Calendar.YEAR) == entry.getStartTime().get(Calendar.YEAR)
                    && startDate.get(Calendar.MONTH) == entry.getStartTime().get(Calendar.MONTH)
                    && startDate.get(Calendar.DAY_OF_MONTH) == entry.getStartTime().get(Calendar.DAY_OF_MONTH))) {
                entryList.add(entry);
            }
        }
        return entryList;
    }

    private Entry getEntryByElement(Element entryElement) {
        Entry entry = new Entry();
        if (entryElement.getAttributeValue("Id") != null) {
            entry.setId(Integer.valueOf(entryElement.getAttributeValue("Id")));
        } else {
            return null;
        }
        entry.setCategory(entryElement.getAttributeValue("Category"));
        entry.setPriority(entryElement.getAttributeValue("Priority"));
        entry.setName(entryElement.getAttributeValue("Name"));
        entry.setDescription(entryElement.getAttributeValue("Description"));
        entry.setAllday(Boolean.parseBoolean(entryElement.getAttributeValue("AllDay")));
        entry.setSpecialEvents(entryElement.getAttributeValue("SpecialEvents", "None"));
        if (entryElement.getAttributeValue("Start_time") != null) {
            StringTokenizer st = new StringTokenizer(entryElement.getAttributeValue("Start_time"));
            entry.setStartTime(new GregorianCalendar(Integer.parseInt(st.nextToken()),
                    Integer.parseInt(st.nextToken()),
                    Integer.parseInt(st.nextToken()),
                    Integer.parseInt(st.nextToken()),
                    Integer.parseInt(st.nextToken())));
        }

        if (entryElement.getAttributeValue("End_time") != null) {
            StringTokenizer st = new StringTokenizer(entryElement.getAttributeValue("End_time"));
            entry.setEndTime(new GregorianCalendar(Integer.parseInt(st.nextToken()),
                    Integer.parseInt(st.nextToken()),
                    Integer.parseInt(st.nextToken()),
                    Integer.parseInt(st.nextToken()),
                    Integer.parseInt(st.nextToken())));
        }

        return entry;
    }

    /**
     * Deletes all entries from {@code entryDoc}.
     */
    public void deleteAllEntries() {
        Element rootTag = entryDoc.getRootElement();
        rootTag.removeContent();
    }
}
